package core;

import gui.GameFrame;

import java.awt.Graphics2D;
import java.awt.event.MouseEvent;
import java.util.HashMap;
import java.util.Queue;

import util.Clock;
import util.Logger;

/**
 * All games which want to use the Void Engine should extend this class.
 * 
 * @author Jason
 * 
 */
public abstract class VoidGameClient implements Runnable {

	private final double ONE_SECOND = 1000.0;

	protected final GameFrame frame;
	private Thread gameThread;
	private final InputManager inputManager;
	private final GameEngine engine = new GameEngine();
	protected GameState currentGameState;
	private final HashMap<String, GameState> gameStates = new HashMap<String, GameState>();
	protected int tickDelay = 0;

	public VoidGameClient() {
		frame = new GameFrame(getName(), false);
		inputManager = new InputManager(frame);
		Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() {
			public void run() {
				shutdown();
			}
		}));
		currentGameState = createGameStates();
		// in case they didn't add the initial one...
		gameStates.put(currentGameState.getName(), currentGameState);
		startGame();
	}

	protected final void addGameState(GameState state) {
		gameStates.put(state.getName(), state);
	}

	/**
	 * Adds the entity to the engine and invokes the handle new entity method.
	 * 
	 * @param e
	 *            The entity to add.
	 */
	protected final void addNewEntity(Entity e) {
		engine.addEntity(e);
		currentGameState.handleNewEntity(e);
	}

	/**
	 * Gets the entity with the specified ID.
	 */
	protected Entity getEntity(int id) {
		return engine.idEntities.get(id);
	}

	@Override
	public void run() {
		long lastTime = Clock.getTime();
		while (gameThread != null) {
			long currentTime = Clock.getTime();
			int elapsed = (int) (currentTime - lastTime);
			updateEngine();
			Queue<MouseEvent> mouseEvents = inputManager.getMouseEvents();
			currentGameState.handle(mouseEvents);
			currentGameState.update(elapsed);
			checkGameState();
			// move our engine forward a frame
			engine.tick(elapsed / ONE_SECOND);
			Graphics2D g = frame.getGraphics();
			if (g == null)
				continue;
			render(g);
			g.dispose();
			frame.update();
			lastTime = currentTime;
			if (tickDelay > 0) {
				try {
					Thread.sleep(tickDelay);
				} catch (InterruptedException e) {
				}
			}
		}
	}

	private void checkGameState() {
		String nextState = currentGameState.getNextState();
		if (nextState != null) {
			GameState gs = gameStates.get(nextState);
			if (gs == null) {
				Logger.log(Logger.SEVERE, "Game State not found for name: "
						+ nextState);
			}
			currentGameState.stop();
			gs.loadResources(null);
			gs.start(inputManager);
			Logger.log(Logger.NORMAL, "Game State changed from "
					+ currentGameState.getName() + " to " + nextState);
			currentGameState = gs;
		}
	}

	/**
	 * Starts the game thread.
	 */
	protected synchronized void startGame() {
		if (gameThread != null) {
			System.err.println("The game is already started.");
			return;
		}
		gameThread = new Thread(this);
		gameThread.start();
	}

	/**
	 * Renders everything on the screen
	 */
	protected void render(Graphics2D g) {
		renderScene(g);
	}

	/**
	 * Renders the scene to the screen.
	 */
	protected void renderScene(Graphics2D g) {
		currentGameState.render(g, frame.getWidth(), frame.getHeight());
		// scene.render(0, 0, f, g);
	}

	/**
	 * A hook for updating the engine from information such as packets!
	 */
	protected void updateEngine() {
	} // allows a hook for updating the engine

	/**
	 * Called when the game is shutting down.
	 */
	protected void shutdown() {
	}

	public GameEngine getEngine() {
		return engine;
	}

	/**
	 * Gets the name of the game.
	 */
	public abstract String getName();

	/**
	 * Adds all of the possible game states.
	 * 
	 * @return The initial game-state to use.
	 */
	public abstract GameState createGameStates();

}
